import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { AliPayEntity } from "./aliPay.entity";
import { Repository } from "typeorm";
import * as crypto from "crypto";
import axios from "axios";
import { signData } from "../../utils/hash";
import { hostUrl } from "../../utils/hostUrl";

@Injectable()
export class AliPayPayment {
  constructor(
    @InjectRepository(AliPayEntity)
    private readonly aliPayEntity: Repository<AliPayEntity>
  ) {}
  /**
   * Obtain parameter
   * @return parameter
   */
  private get aliPayConfig() {
    return this.aliPayEntity.findOne({
      where: {
        id: 1
      }
    })
  }
  /**
   * Generate payment link
   * @param query
   */
  public async generateUrl(query: any): Promise<string> {
    const { total, title, order } = query // order amount, order title, order number
    const { appid } = await this.aliPayConfig
    const requestConfigs = {
      out_trade_no: order, // order number
      total_amount: total,
      subject: title,
      timeout_express: "2h"
    }
    const commonConfigs = {
      app_id: appid,
      method: "alipay.trade.precreate",
      format: "JSON",
      charset: "utf-8",
      sign_type: "RSA2",
      timestamp: this.getCurrentDateTime(),
      version: "1.0",
      notify_url: hostUrl("/api/aliPay/notify"),
      biz_content: JSON.stringify(requestConfigs)
    }
    const sign = await this.sign(commonConfigs) // signature
    const data = {
      ...commonConfigs,
      sign
    }
    return await this.postRequest(data)
  }

  /**
   * Generate timestamp string
   * @example 2023-08-11 10:30:00
   */
  private getCurrentDateTime(): string {
    return new Date().toISOString().replace(/T/, " ").replace(/\..+/, "")
  }

  /**
   * Signature
   * @param params
   */
  private async sign(params: any): Promise<string> {
    const message = this.getSignContent(params)
    const { private_key } = await this.aliPayConfig
    const res = `-----BEGIN RSA PRIVATE KEY-----\n${private_key
      .match(/.{1,64}/g)
      .join("\n")}\n-----END RSA PRIVATE KEY-----`
    return signData(message, res)
  }

  /**
   * Generate signature string
   * @param params
   * @description Arrange and combine request parameters based on key values, and convert them into signature strings
   */
  private getSignContent(params: { [key: string]: any }): string {
    const sortedParams = Object.keys(params).sort((a, b) => a.localeCompare(b))
    let stringToBeSigned = ""
    for (const key of sortedParams) {
      const value = params[key]
      stringToBeSigned += `${stringToBeSigned ? "&" : ""}${key}=${value}`
    }
    return stringToBeSigned
  }

  /**
   * Initiation request
   * @param data
   * @description The request is in the form of a URL request
   */
  private async postRequest(data: object): Promise<string> {
    const urlParams = new URLSearchParams()
    for (const key in data) {
      if (Object.prototype.hasOwnProperty.call(data, key)) {
        urlParams.append(key, data[key])
      }
    }
    const url = `https://openapi.alipay.com/gateway.do?${urlParams.toString()}`
    const result = await axios.post(url)
    return result.data["alipay_trade_precreate_response"]["qr_code"]
  }

  /**
   * Payment callback verification signature
   * @param body
   */
  public async rsaCheck(body: any) {
    const { sign } = body
    delete body.sign
    delete body.sign_type
    const message = this.getSignContent(body)
    const { public_key } = await this.aliPayConfig
    const pemKey = `-----BEGIN PUBLIC KEY-----\n${public_key
      .match(/.{1,64}/g)
      .join("\n")}\n-----END PUBLIC KEY-----`
    const verifySign = crypto.createVerify("sha256")
    verifySign.update(message)
    return verifySign.verify(pemKey, Buffer.from(sign, "base64"))
  }
}
